package com.mandao.oneplat.boot.ioc;

import com.mandao.oneplat.boot.OnePlatConfig;
import com.mandao.oneplat.boot.dao.OneMongoDao;
import com.mandao.oneplat.boot.util.CollectionUtil;
import com.mandao.oneplat.boot.util.PackageUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by sunson on 2017/3/29.
 */
public class OneIocFactory {

    static Logger logger = LoggerFactory.getLogger(OneIocFactory.class);

    final static List<Object> controllers = new ArrayList<>();
    final static Map<String, Object> iocServiceMap = new HashMap<>();

    public static <T> T getBean(String name) {
        return (T) iocServiceMap.get(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        AOneService aOneService = clazz.getAnnotation(AOneService.class);
        if (aOneService == null) {
            return null;
        }
        String name = aOneService.value();
        if (StringUtils.isEmpty(name)) {
            name = clazz.getSimpleName();
        }
        return (T) iocServiceMap.get(name);
    }

    /**
     * 扫描 AOneController 和 AOneService
     *
     * @param oneOption
     * @param oneMongoDao
     */
    public static void initIoc(OnePlatConfig oneOption, OneMongoDao oneMongoDao) throws IOException, IllegalAccessException, InstantiationException, InvocationTargetException {
        if (StringUtils.isEmpty(oneOption.getBasePackages())) {
            return;
        }

        List<Class> classList = PackageUtil.scanPackages(oneOption.getBasePackages());
        //
        if (CollectionUtil.isEmpty(classList)) {
            return;
        }
        logger.info("扫描到的类,{}", classList);
        for (Class clazz : classList) {
            String cname;
            Object cinstance = clazz.newInstance();
            if (clazz.isAnnotationPresent(AOneController.class)) {
                controllers.add(cinstance);
            } else if (clazz.isAnnotationPresent(AOneService.class)) {
                AOneService aOneService = (AOneService) clazz.getAnnotation(AOneService.class);
                cname = aOneService.value();
                if (StringUtils.isEmpty(cname)) {
                    cname = clazz.getSimpleName();
                }
                iocServiceMap.put(cname, cinstance);
            }
        }
        // 设置Controller依赖
        for (Object control : controllers) {
            iocDependcy(control, oneMongoDao);
        }
        // 设置Service依赖
        for (Map.Entry entry : iocServiceMap.entrySet()) {
            iocDependcy(entry.getValue(), oneMongoDao);
        }
        // Service初始化
        for (Map.Entry entry : iocServiceMap.entrySet()) {
            serviceInit(entry.getValue());
        }
        // 初始化成功
        logger.info("服务初始化成功,{}", iocServiceMap.keySet());
    }

    static void serviceInit(Object instance) throws InvocationTargetException, IllegalAccessException {
        for (Method m : MethodUtils.getMethodsWithAnnotation(instance.getClass(), AOneServiceInit.class)) {
            m.invoke(instance);
            break;
        }
    }

    static void iocDependcy(Object instance, OneMongoDao oneMongoDao) throws IllegalAccessException {
        for (Field f : FieldUtils.getAllFields(instance.getClass())) {
            f.setAccessible(true);
            for (Annotation annotation : f.getDeclaredAnnotations()) {
                if (AOneDao.class.isAssignableFrom(annotation.annotationType())) {
                    f.set(instance, oneMongoDao);
                } else if (AOneService.class.isAssignableFrom(annotation.annotationType())) {
                    f.set(instance, getBean(instance.getClass()));
                }
            }
            f.setAccessible(false);
        }
    }

    public static List<Object> getControllers() {
        return controllers;
    }
}
